﻿using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.AnimatedValues;
using UnityEngine;

namespace CWM.Skinn
{
    public partial class BoneHandles
    {
        public Transform transform;
        public List<BoneElement> skeleton;

        public BoneRenderer boneRenderer = new BoneRenderer();

        public List<int> selection = new List<int>() {-1};

        public Action OnPreTranslate;
        public Action OnUpdated;
        public Action OnSelected;

        public float poseWeight;

        public AnimFloat animatePose = new AnimFloat(1f);

        private int HandleHash { get { return "WeightCanvasHandleHash".GetHashCode(); } }
        public Vector2 HandleMouseStart { get; set; } //= Vector2.zero;
        public Vector2 HandleMouseCurrent { get; set; }// = Vector2.zero;
        public Vector3 HandleWorldStart { get; set; } //= Vector3.zero;
        public float HandleClickTime { get; set; } //= 0f;
        public int HandleClickID { get; set; } //= 0;
        public bool HasChanged { get; set; } //= false;

        private bool PoseLock = true;
        private bool Transforming = false;
        private Tool lastTool = Tool.None;

        const float handleDoubleClickInterval = 0.5f;

        public bool enabled = true;
        public bool useBonePose = true;

        private static readonly Color colorOn = new Color(0f, 0f, 0f, 0.8f);
        private static readonly Color colorOff = new Color(0.5f, 0.5f, 0.5f, 0.013f);
        private static readonly  Color colorClicked = new Color(0f, 0f, 1f, 1);
        private static readonly Color colorSelected = new Color(0f, 1f, 1f, 0.5f);
        private static readonly Color colorFirstSelected = new Color(0f, 1f, 1f, 0.8f);

        public static implicit operator bool(BoneHandles handles) { return handles != null; }

        public void Pose()
        {
            poseWeight = animatePose.value;
            if (animatePose.target == 0f && animatePose.value < 0.01f) animatePose.target = 1f;
            if (OnUpdated != null) OnUpdated();
        }

        public void OnSceneGUI(List<BoneElement> skeleton, Transform transform)
        {
            if (!enabled) return;
            if (SkinnEx.IsNullOrEmpty(skeleton)) return;
            this.transform = transform;
            this.skeleton = skeleton;

            SceneView sceneView = SceneView.currentDrawingSceneView;
            if (SceneView.lastActiveSceneView != sceneView) return;
            if (sceneView.in2DMode) sceneView.in2DMode = false;
            if (sceneView.orthographic) sceneView.orthographic = false;

            //if (Tools.current == Tool.View) { }
            //else { TranslateSkeleton(); }

            boneRenderer.combinedMesh.Initialize(boneRenderer, Mathf.Clamp(skeleton.Count - 1, 0, 10000000));
            KeyCommands(sceneView);
            BoneRenderSelector(skeleton[0], sceneView);

            boneRenderer.BuildMeshes();
            boneRenderer.Draw(null);

            if (lastTool != Tools.current)
            {
                lastTool = Tools.current;
                if (OnUpdated != null) OnUpdated();

            }
        }

        private void TranslateSkeleton()
        {
            bool worldSpace = Tools.pivotRotation == PivotRotation.Global ? true : false, skeletonChanged = false;
            for (int i = 1; i < skeleton.Count; i++)
            {
                if (SkinnEx.IsNullOrEmpty(selection)) return;
                if (selection[0] != skeleton[i].ID) continue;

                SkinnGizmos.ResetTransforms(transform);
                Transform trans = SkinnGizmos.Transform0;

                if (!PoseLock)trans.FromMatrix(skeleton[i].bindpose2.inverse);
                else trans.FromMatrix(skeleton[i].bindpose.inverse);

                //TODO Rotate WorldSpace
                //TODO Rotate CenterBone

                if (Tools.current == Tool.Move)
                {
                    EditorGUI.BeginChangeCheck();
                    {
                        trans.position = Handles.DoPositionHandle(trans.position, worldSpace ? Quaternion.identity : trans.rotation);
                    }
                    if (EditorGUI.EndChangeCheck())
                    {
                        skeletonChanged = true;
                    }
                }

                if (Tools.current == Tool.Rotate)
                {

                    EditorGUI.BeginChangeCheck();
                    {
                        trans.rotation = Handles.DoRotationHandle(trans.rotation, trans.position);
                    }
                    if (EditorGUI.EndChangeCheck())
                    {
                        skeletonChanged = true;
                    }
                }

                if (Tools.current == Tool.Scale)
                {
                    float handleSize = HandleUtility.GetHandleSize(trans.position);
                    EditorGUI.BeginChangeCheck();
                    {
                        trans.localScale = Vector3.Max(Handles.ScaleHandle(trans.localScale, trans.position, trans.rotation, 1f * handleSize), Vector3.one * 0.0001f);
                    }
                    if (EditorGUI.EndChangeCheck())
                    {
                        skeletonChanged = true;
                    }
                }

                if (!skeletonChanged) continue;

                trans.SetParent(null, true);
                TransformRecursive(PoseLock ? skeleton[i].bindpose : skeleton[i].bindpose2, trans.localToWorldMatrix.inverse, skeleton[i]);

                if (!PoseLock)
                    skeleton[i].bindpose2 = trans.localToWorldMatrix.inverse * transform.worldToLocalMatrix.inverse;
                else
                {
                    skeleton[i].bindpose2 = trans.localToWorldMatrix.inverse * transform.worldToLocalMatrix.inverse;
                    skeleton[i].bindpose = trans.localToWorldMatrix.inverse * transform.worldToLocalMatrix.inverse;
                }
            }

            if (skeletonChanged)
            {
                if (Transforming == false)
                {
                    if(OnPreTranslate != null) OnPreTranslate();
                }

                Transforming = true;
                if (OnUpdated != null) OnUpdated();
            }

        }

        private void TransformRecursive(Matrix4x4 pose, Matrix4x4 newPose, BoneElement element)
        {
            if (!element.HasChildren || element.Children == null)
                return;
            for (int i = 0; i < element.Children.Count; ++i)
            {
                BoneElement currentBone = element.Children[i] as BoneElement;

                SkinnGizmos.ResetTransforms();
                Transform root = SkinnGizmos.Transform0;
                Transform child = SkinnGizmos.Transform1;

                root.FromMatrix(pose.inverse);

                if (!PoseLock) child.FromMatrix(currentBone.bindpose2.inverse);
                else child.FromMatrix(currentBone.bindpose.inverse);

                child.SetParent(root, true);
                root.FromMatrix(newPose.inverse);
                root.SetParent(transform, true);

                if (!PoseLock)
                    currentBone.bindpose2 = child.localToWorldMatrix.inverse * this.transform.localToWorldMatrix;
                else
                {
                    currentBone.bindpose2 = child.localToWorldMatrix.inverse * this.transform.localToWorldMatrix;
                    currentBone.bindpose = child.localToWorldMatrix.inverse * this.transform.localToWorldMatrix;
                }

                if (Tools.current != Tool.Scale) TransformRecursive(pose, newPose, currentBone);
            }
        }
       
        private void KeyCommands(SceneView sceneView)
        {
            if (Event.current.type == EventType.ExecuteCommand)
            {
                //Debug.Log(Event.current.commandName);
                if (Event.current.commandName == "FrameSelected")
                {
                    Event.current.Use();
                }
            }
            switch (Event.current.GetTypeForControl(0))
            {
                case EventType.KeyDown:

                    if (Event.current.isKey && Event.current.keyCode == KeyCode.F)
                    {
                        if (Tools.current != Tool.View && selection.Count > 0)
                        {
                            BoneElement bone = skeleton.GetBoneTreeElement(selection[0], true);
                            SceneView.lastActiveSceneView.FrameSelected();
                            SceneView.lastActiveSceneView.Frame(new Bounds
                            {
                                center = bone.bindpose2.inverse.ExtractPosition(),
                                extents = Vector3.one * 0.25f //TODO// Focus on Weights, Options.
                            }, false);
                            Event.current.Use();
                        }
                        else
                        {
                            SceneView.lastActiveSceneView.FrameSelected();
                            //SceneView.lastActiveSceneView.Frame(DeltaMesh.GetBoundsRecalculateIfNeeded().Transformed(this.transform));
                            Event.current.Use();
                        }
                    }
                    break;
            }
        }

        private void BoneRenderSelector(BoneElement treeElement, SceneView sceneView, bool hasNodes = false)
        {
            List<TreeElement> elements = !treeElement.HasChildren || treeElement.Children == null ? new List<TreeElement>() : treeElement.Children;

            float posing = PoseLock ? 0 : poseWeight;
            Vector3 parentBindpose = treeElement.bindpose.inverse.ExtractPosition();
            parentBindpose = this.transform.TransformPoint(parentBindpose);
            Vector3 parentPose = treeElement.bindpose2.inverse.ExtractPosition();
            parentPose = this.transform.TransformPoint(parentPose);
            Vector3 parentPosition = Vector3.Lerp(parentBindpose, parentPose, posing);
            parentPosition = Handles.matrix.MultiplyPoint(parentPosition);

            parentPosition = treeElement.bonePose.ExtractPosition();
            parentPosition = this.transform.TransformPoint(parentPosition);
            parentPosition = Handles.matrix.MultiplyPoint(parentPosition);

            Matrix4x4 handleMatrix = Handles.matrix;
            Color handleColor = Handles.color;
            float handleSize = 0.01f;

            for (int i = 0; i < elements.Count; ++i)
            {
                BoneElement currentTree = elements[i] as BoneElement;

                int id = GUIUtility.GetControlID(HandleHash, FocusType.Passive);

                Vector3 childBindpose = currentTree.bindpose.inverse.ExtractPosition();
                childBindpose = this.transform.TransformPoint(childBindpose);
                Vector3 childPose = currentTree.bindpose2.inverse.ExtractPosition();
                childPose = this.transform.TransformPoint(childPose);
                Vector3 childPosition = Vector3.Lerp(childBindpose, childPose, posing);
                childPosition = Handles.matrix.MultiplyPoint(childPosition);

                childPosition = currentTree.bonePose.ExtractPosition();
                childPosition = this.transform.TransformPoint(childPosition);
                childPosition = Handles.matrix.MultiplyPoint(childPosition);

                switch (Event.current.GetTypeForControl(id))
                {
                    case EventType.MouseDown:
                        if (HandleUtility.nearestControl == id && (Event.current.button == 0 || Event.current.button == 1))
                        {
                            GUIUtility.hotControl = id;
                            HandleMouseCurrent = HandleMouseStart = Event.current.mousePosition;
                            HasChanged = false;
                            Event.current.Use();
                            EditorGUIUtility.SetWantsMouseJumping(1);
                        }
                        break;

                    case EventType.MouseUp:
                        if (GUIUtility.hotControl == id && (Event.current.button == 0 || Event.current.button == 1))
                        {
                            GUIUtility.hotControl = 0;
                            Event.current.Use();
                            EditorGUIUtility.SetWantsMouseJumping(0);
                            if (Event.current.mousePosition == HandleMouseStart)
                            {
                                //bool doubleClick = (handleClickID == id) && (Time.realtimeSinceStartup - handleClickTime < handleDoubleClickInterval);
                                HandleClickID = id;
                                HandleClickTime = Time.realtimeSinceStartup;
                            }
                        }
                        break;
                    case EventType.MouseDrag:
                        if (GUIUtility.hotControl == id) {HasChanged = true; GUI.changed = true; Event.current.Use(); }
                        break;
                    case EventType.Repaint:

                        Color handlesColor = treeElement.enabled ? colorOn : colorOff;
                        if (id == GUIUtility.hotControl)
                        {
                            handlesColor = colorClicked;
                            if (!selection.Contains(currentTree.ID)) selection = new List<int>(1) { currentTree.ID };
                            if (OnSelected != null) OnSelected();
                        }

                        for (int ii = 0; ii < selection.Count; ++ii)
                            if (ii < 1 && selection[ii] == currentTree.ID) handlesColor = colorFirstSelected;
                            else if (selection[ii] == currentTree.ID) handlesColor = colorSelected;

                        float distance = (childPosition - parentPosition).magnitude;
                        float boneSize = distance * 2f / 1f * 0.1f;
                        boneSize = Mathf.Clamp(boneSize, 0.0001f, distance * 0.5f);

                        if (treeElement.Children.Count < 7 && currentTree.HasChildren)
                            boneRenderer.AddBone(childPosition, parentPosition, handlesColor, boneSize, true);
                        else
                        {
                            if (currentTree.HasChildren)
                            {
                                handlesColor.a = 0.25f;
                                boneRenderer.AddBone(childPosition, parentPosition, handlesColor, boneSize * 0.1f, true);
                            }
                            else boneRenderer.AddBone(childPosition, parentPosition, handlesColor, boneSize, false);

                            //boneRenderer.DrawLine(childPosition, parentPosition, handlesColor);
                        }

                        break;

                    case EventType.Layout:
                        if (Tools.current != Tool.View)
                        {
                            Handles.matrix = Matrix4x4.identity;
                            HandleUtility.AddControl(id, HandleUtility.DistanceToCircle(childPosition, handleSize));

                            if (treeElement.Children.Count < 7 && currentTree.HasChildren)
                                HandleUtility.AddControl(id, HandleUtility.DistanceToLine(childPosition, parentPosition));
                            else
                                if (!currentTree.HasChildren) HandleUtility.AddControl(id, HandleUtility.DistanceToLine(childPosition, parentPosition));
                            Handles.matrix = handleMatrix;
                        }
                        break;
                }
                BoneRenderSelector(currentTree, sceneView, hasNodes);
            }
        }


    }
}
